/**
 *
 * @author Doan Tuan Anh
 */

import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.Image;
import javax.microedition.media.Player;
        
public class Duck extends Sprite{
    
    int sx, sy;
    private static final int TOTAL_FRAME = 95;
    private final int NUM_DIVING_FRAME = 80;
    private int FRAME_WIDTH, FRAME_HEIGHT;
    private int[] INITIAL_SEQUENCE = {0, 1, 2, 3, 4};
    private int[] NORMAL_SEQUENCE;
    Splash splash;
    private boolean hurting;
    private int WIDTH, HEIGHT;
    
    //calculate combo
    private Player waterSound, eatingSound;
    private boolean finishDiving, hurted;
    private int numCombo;
    
    public Duck(int initialX, int initialY, Splash splash, Player waterSound, Player eatingSound) throws Exception {
    
        super(Image.createImage("/duck.PNG"), Image.createImage("/duck.PNG").getWidth() / TOTAL_FRAME, Image.createImage("/duck.PNG").getHeight());
    
        WIDTH = super.getWidth();
        HEIGHT = super.getHeight();
        
        defineReferencePixel(super.getWidth() / 2, super.getHeight() / 2);

        setFrameSequence(INITIAL_SEQUENCE);
        NORMAL_SEQUENCE = new int[TOTAL_FRAME];
        for(int i = 0; i < TOTAL_FRAME; i++) NORMAL_SEQUENCE[i] = i;
        
        this.waterSound = waterSound;
        this.eatingSound = eatingSound;
        
        hurting = false;
        running = false;
        diving = false;
        finishDiving = false;
        hurted = false;
        numCombo = 0;
        
        sx = initialX;
        sy = initialY;
        right = true;
        setRefPixelPosition(sx, sy);
        
        this.splash = splash;
    }
    
    private boolean right;
    private boolean diving;
    
    void startEatingSound()
    {
        try
        {
            eatingSound.start();
        }
        catch(Exception e) {}
    }
    
    void setWidth(int width)
    {
        FRAME_WIDTH = width;
    }
    
    void setHeight(int height)
    {
        FRAME_HEIGHT = height;
    }
    
    void reset() {
        hurting = false;
        diving = false;
        running = false;
        right = true;
        finishDiving = false;
        hurted = false;
        numCombo = 0;
        
        curPoint = 0;
        numRunning = 0;
        
        setVisible(true);
        setFrameSequence(INITIAL_SEQUENCE);
        setRefPixelPosition(sx, sy);
    }
    
    boolean isDiving()
    {
        return diving;
    }
    
    int getInitialY()
    {
        return sy;
    }
    
    private int[] xs, ys;
    private int[] DIVING_SEQUENCE;
    private int[] EATING_SEQUENCE;
    
    final int NUM_POINTS = 15;
    private int curPoint, totalPoints;
    private int[] DEGREES = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75};
    
    void calc(int[] xs, int[] ys, int i, int j, int type, int from)
    {
        int x0 = xs[i];
        int y0 = ys[i];
        
        int x1 = type == 0 ? xs[i] : xs[j];
        int y1 = type == 0 ? ys[j] : ys[i];
        
        int x2 = xs[j];
        int y2 = ys[j];
        
        from++;
        double t = 1. / NUM_POINTS;
        for(int k = i + 1; k < j; k++)
        {
            xs[k] = f(x0, x1, x2, t * (k - i));
            ys[k] = f(y0, y1, y2, t * (k - i));
            DIVING_SEQUENCE[from] = type == 0 ? calcAngle(xs[k], ys[k], xs[j], ys[j], type % 5) : DIVING_SEQUENCE[2 * (NUM_POINTS + 1) - from];
            EATING_SEQUENCE[from] = Math.max(NUM_DIVING_FRAME, NUM_DIVING_FRAME + DIVING_SEQUENCE[from] / 5 - 1);
            from++;
        }
        DIVING_SEQUENCE[type] = type % 5;
        EATING_SEQUENCE[type] = NUM_DIVING_FRAME;
    }
    
    int calcAngle(int x1, int y1, int x2, int y2, int order)
    {
        int x3, y3;
        if(x1 > x2)
        {
            x3 = x2 + 1;
            y3 = y2;
        }
        else
        {
            x3 = x2 - 1;
            y3 = y2;
        }
        
        double cos_alpha = dot(x1 - x2, y1 - y2, x3 - x2, y3 - y2) / dist(x1, y1, x2, y2);
        
        double best = Double.MAX_VALUE;
        int j = 0;
        for(int i = 0; i < DEGREES.length; i++)
        {
            if(Math.abs(cos_alpha - Math.cos(Math.toRadians(DEGREES[i]))) < best)
            {
                best = Math.abs(cos_alpha - Math.cos(Math.toRadians(DEGREES[i])));
                j = i;
            }
        }
        return j * 5 + order;
    }
    
    double dot(int x1, int y1, int x2, int y2)
    {
        return x1 * x2 + y1 * y2;
    }
    
    double dist(int x1, int y1, int x2, int y2)
    {
        return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
    }
    
    int f(double p0, double p1, double p2, double t)
    {
        return (int)Math.floor(p0 * (1 - t) * (1 - t) + p1 * 2 * (1 - t) * t + p2 * t * t);
    }
    
    //reflection
    void fix(int[] xs, int[] ys)
    {
        int n = xs.length;
        for(int i = 0; i < n; i++)
        {
            if(xs[i] > FRAME_WIDTH) xs[i] -= FRAME_WIDTH;
            if(xs[i] < 0) xs[i] += FRAME_WIDTH;
        }
    }
    
    void setDiving(int X, int Y)
    {
        diving = true;
        setFrameSequence(NORMAL_SEQUENCE);
        curPoint = 0;
        
        totalPoints = NUM_POINTS * 2 + 3;
        
        xs = new int[totalPoints];
        ys = new int[totalPoints];
        DIVING_SEQUENCE = new int[totalPoints];
        EATING_SEQUENCE = new int[totalPoints];
        
        int st = 0;
        int mid = NUM_POINTS + 1;
        int en = NUM_POINTS * 2 + 2;
        
        xs[st] = getRefPixelX();
        ys[st] = sy;
        
        xs[mid] = X;
        ys[mid] = Y;
        
        xs[en] = -(getRefPixelX() - X) + X;
        ys[en] = sy;
        
        calc(xs, ys, st, mid, 0, 0);
        calc(xs, ys, mid, en, 1, NUM_POINTS + 1);
        
        fix(xs, ys);
    }
    
    int checkCollision(ClownFish clownFish) {
        
        if(hurting) return 0;
        
        int res = 0;
        if(collidesWith(clownFish, true)) {
          res++;
          clownFish.reset();
        }
        return res;
    }
    
    int checkCollision(GoldFish goldFish)
    {
        if(hurting) return 0;
        
        int res = 0;
        if(collidesWith(goldFish, true))
        {
            res += 2;
            goldFish.reset();
        }
        return res;
    }
    
    int checkCollision(Piranha piranha)
    {
        if(hurting) return 0;
        
        int res = 0;
        if(collidesWith(piranha, true))
        {
            res--;
            hurting = true;
        }
        return res;
    }
   
    void setOrient(boolean right)
    {
        this.right = right;
    }
    
    private boolean running;
    
    boolean isRunning()
    {
        return running;
    }
    
    int[] runningX;
    int numRunning = 0;
    int RUNNING_DIST = 6;
    
    void setRunning(int X)
    {
        running = true;
        setFrameSequence(INITIAL_SEQUENCE);
        numRunning = 0;
        
        int curX = getRefPixelX();
        int dist = Math.abs(curX - X);
        runningX = new int[dist / RUNNING_DIST + 2];
        int sign = X - curX < 0 ? -1 : 1;
        runningX[0] = curX;
        runningX[runningX.length - 1] = X;
        
        int inc = RUNNING_DIST;
        for(int i = 1; i < runningX.length - 1; i++) runningX[i] = runningX[i - 1] + sign * inc;
    }
    
    boolean hasFinishedDiving()
    {
        return finishDiving;
    }
    
    void resetFinishDiving()
    {
        finishDiving = false;
    }
    
    boolean wasHurted()
    {
        return hurted;
    }
    
    void setHurted()
    {
        hurted = true;
    }
    
    void resetHurted()
    {
        hurted = false;
    }
    
    void addCombo(int num)
    {
        numCombo += num;
    }
    
    void resetCombo()
    {
        numCombo = 0;
    }   
    
    int getCombo()
    {
        return numCombo;
    }
    
    void advance(int tickCount, boolean eating) {
        if(right) {
          setTransform(TRANS_MIRROR);
        } else {
          setTransform(TRANS_NONE);
        }
        
        if(running)
        {
            numRunning++;
            
            if(numRunning == runningX.length)
            {
                diving = false;
                hurting = false;
                running = false;
                numRunning = 0;
                setVisible(true);
                setFrameSequence(INITIAL_SEQUENCE);
                return;
            }
            
            setRefPixelPosition(runningX[numRunning], sy);
            nextFrame();
        }
        else if(diving)
        {
            curPoint++;
            
            if(curPoint == totalPoints)
            {
                diving = false;
                hurting = false;
                finishDiving = true;
                setVisible(true);
                setFrameSequence(INITIAL_SEQUENCE);
                return;
            }
            
            if(curPoint == 1) splash.setRunning(xs[curPoint - 1], sy);
            if(curPoint == totalPoints - 2)
            {
                splash.setRunning(xs[curPoint + 1], sy);
                try
                {    
                    waterSound.start();
                }
                catch(Exception e) {}
            } 
            
            if(hurting)
            {
                if(curPoint % 2 == 0) setVisible(false);
                else setVisible(true);
            }
               
            int degree = DIVING_SEQUENCE[curPoint] / 5 * 5;
            
            if(!eating)
                setFrame(DIVING_SEQUENCE[curPoint]);
            else
            {
                setFrame(EATING_SEQUENCE[curPoint]);
            }
            
            if(degree != 0 && curPoint > NUM_POINTS + 1) //pass mid point
            {
                if(right) setTransform(TRANS_MIRROR_ROT270);
                else setTransform(TRANS_ROT90);
            }
            setRefPixelPosition(xs[curPoint], ys[curPoint]);
        }
        else
        {
            hurting = false;
            nextFrame();
        }
    }  
}
